package ph.com.gs3.formalistics.presenter;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import jim.h.common.android.zxinglib.integrator.IntentIntegrator;
import jim.h.common.android.zxinglib.integrator.IntentResult;

import org.json.JSONObject;

import ph.com.gs3.formalistics.R;
import ph.com.gs3.formalistics.custom.interfaces.CustomDocumentActivity;
import ph.com.gs3.formalistics.global.interfaces.CallbackCommand;
import ph.com.gs3.formalistics.global.utilities.logging.FLLogger;
import ph.com.gs3.formalistics.model.valueobjects.business.User;
import ph.com.gs3.formalistics.model.valueobjects.business.document.AbstractDocument;
import ph.com.gs3.formalistics.model.valueobjects.business.document.Document;
import ph.com.gs3.formalistics.model.valueobjects.business.document.DocumentAction;
import ph.com.gs3.formalistics.model.valueobjects.business.document.Document.DocumentStar;
import ph.com.gs3.formalistics.model.valueobjects.business.form.Form;
import ph.com.gs3.formalistics.model.valueobjects.business.form.FormField;
import ph.com.gs3.formalistics.presenter.DateTimePickerActivity.DateTimePickerType;
import ph.com.gs3.formalistics.presenter.fragments.views.DocumentViewFragment;
import ph.com.gs3.formalistics.presenter.fragments.views.DocumentViewFragment.DocumentViewActionListener;
import ph.com.gs3.formalistics.presenter.fragments.workers.DocumentWorkerFragment;
import ph.com.gs3.formalistics.service.services.OutgoingDocumentActionSubmitService;
import ph.com.gs3.formalistics.service.services.OutgoingStarMarksSubmitService;
import ph.com.gs3.formalistics.view.custom.fields.BarcodeScannerField;
import ph.com.gs3.formalistics.view.custom.fields.DateTimePickerField;
import ph.com.gs3.formalistics.view.custom.fields.BarcodeScannerField.BarcodeScannerListener;
import ph.com.gs3.formalistics.view.custom.fields.DateTimePickerField.DateTimePickerListener;
import ph.com.gs3.formalistics.view.custom.fields.DateTimePickerField.PickerType;
import ph.com.gs3.formalistics.view.dialogs.DocumentActionsDialogFragment;
import ph.com.gs3.formalistics.view.valueobjects.parsers.DateUtilities;
import android.app.Activity;
import android.app.FragmentManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

public class DocumentActivity extends Activity implements DocumentViewActionListener,
        DateTimePickerListener, BarcodeScannerListener {

	// ========================================================================
	// {{ Constants

	public static final String TAG = DocumentActivity.class.getSimpleName();

	public static final String EXTRA_CURRENT_USER = "current_user";
	public static final String EXTRA_DOCUMENT_TYPE = "document_type";
	public static final String EXTRA_FORM_DB_ID = "form_db_id";
	public static final String EXTRA_DOCUMENT_DB_ID = "document_db_id";

	// }}

	// ========================================================================
	// Customization Interface

	public CustomDocumentActivity customDocumentActivity;

	// ========================================================================
	// Dependencies

	private DocumentViewFragment documentViewFragment;
	private DocumentWorkerFragment documentWorkerFragment;

	// ========================================================================
	// Fields & Enumerations

	private User currentUser;
	private DocumentType documentType;

	private String formDbId;
	private String documentDbId;

	private String activeBarcodeScannerFieldId;

	public static enum DocumentType {
		NEW_DOCUMENT, EXISTING_DOCUMENT, OUTGOING_DOCUMENT
	}

	// ========================================================================
	// {{ Life Cycle Listeners

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_document);

		initializeFieldsFromExtras();
		initializeWorkerFragment();
		registerBroadcastReceivers();

		if (savedInstanceState == null) {

			Form form = documentWorkerFragment.getCurrentForm();
			AbstractDocument document;

			if (documentType == DocumentType.NEW_DOCUMENT
			        || documentType == DocumentType.EXISTING_DOCUMENT) {
				document = documentWorkerFragment.getCurrentDocument();
			} else if (documentType == DocumentType.OUTGOING_DOCUMENT) {
				document = documentWorkerFragment.getCurrentOutgoingDocument();
			} else {
				throw new IllegalArgumentException("Unable to recognize document type");
			}

			documentViewFragment = DocumentViewFragment
			        .createInstance(form, document, documentType);
			getFragmentManager().beginTransaction().add(R.id.container, documentViewFragment)
			        .commit();

			setTitle(form.getName());

		}

		// for custom implementations
		customDocumentActivity = CustomImplementation.getInstance().getCustomDocumentActivity(this);

	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {

		if (documentType == DocumentType.NEW_DOCUMENT) {
			getMenuInflater().inflate(R.menu.document, menu);
		} else if (documentType == DocumentType.EXISTING_DOCUMENT) {

			Document document = documentWorkerFragment.getCurrentDocument();

			FLLogger.d(TAG, document.getProcessorWebId() + " = " + currentUser.getWebId());
			if (document.getProcessorWebId().equals(Integer.toString(currentUser.getWebId()))) {
				FLLogger.d(TAG, "displaying for approval");
				getMenuInflater().inflate(R.menu.document_for_approval, menu);
			} else {
				FLLogger.d(TAG, "not displaying for approval");
			}

		}

		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle action bar item clicks here. The action bar will
		// automatically handle clicks on the Home/Up button, so long
		// as you specify a parent activity in AndroidManifest.xml.
		int id = item.getItemId();
		switch (id) {
			case R.id.action_submit_action: {
				onSubmitActionCommand();

				return true;
			}
		}

		return super.onOptionsItemSelected(item);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		if (data != null) {
			switch (requestCode) {
				case DateTimePickerActivity.RC_PICK_DATE:
				case DateTimePickerActivity.RC_PICK_TIME:
				case DateTimePickerActivity.RC_PICK_DATE_TIME: {
					if (data.hasExtra(DateTimePickerActivity.EXTRA_RESULT_SELECTED_DATE)) {
						String fieldId = data
						        .getStringExtra(DateTimePickerActivity.EXTRA_DATE_TIME_PICKER_FIELD_ID);
						Date updatedDate = (Date) data
						        .getSerializableExtra(DateTimePickerActivity.EXTRA_RESULT_SELECTED_DATE);
						updateDateTimePickerView(requestCode, fieldId, updatedDate);
					}

				}

				case IntentIntegrator.REQUEST_CODE: { // Bar code scanner
					IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode,
					        resultCode, data);
					if (scanResult == null) {
						return;
					}
					final String result = scanResult.getContents();
					if (result != null) {
						Handler handler = new Handler();
						handler.post(new Runnable() {
							@Override
							public void run() {
								// custom events
								if (customDocumentActivity != null) {
									customDocumentActivity.onBarcodeScanned(
									        activeBarcodeScannerFieldId, result);
								}

								documentViewFragment.updateBarcodeScannerFieldValue(
								        activeBarcodeScannerFieldId, result);
							}
						});

					}
				}
					break;
			}

		}

	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		unregisterBroadcastReceivers();
	}

	// }}

	// ========================================================================
	// {{ Dialog View Methods

	private void showActionsDialog() {

		DocumentActionsDialogFragment dialog = new DocumentActionsDialogFragment();
		List<DocumentAction> actions;

		Form currentForm = documentWorkerFragment.getCurrentForm();
		Document currentDocument = documentWorkerFragment.getCurrentDocument();

		if (documentType == DocumentType.NEW_DOCUMENT) {
			actions = currentForm.getActions();
		} else if (documentType == DocumentType.EXISTING_DOCUMENT) {
			actions = currentDocument.getAvailableActions();
		} else {
			actions = new ArrayList<DocumentAction>();
		}

		dialog.setActions(actions);
		dialog.show(getFragmentManager(), DocumentActionsDialogFragment.TAG);
		dialog.setOnActionSelectedCallback(new CallbackCommand<DocumentAction>() {

			@Override
			public void execute(DocumentAction clickedAction) {
				// submitDocumentAction(clickedAction.getAction());
				submitDocumentAction(clickedAction.getLabel());
			}
		});
	}

	// }}

	// ========================================================================
	// {{ Initializer Methods

	private void initializeFieldsFromExtras() {

		Bundle extras = getIntent().getExtras();

		currentUser = (User) extras.getSerializable(EXTRA_CURRENT_USER);
		documentType = (DocumentType) extras.get(EXTRA_DOCUMENT_TYPE);

		if (documentType == DocumentType.NEW_DOCUMENT) {
			formDbId = extras.getString(EXTRA_FORM_DB_ID);
		} else {	// existing or outgoing document
			documentDbId = extras.getString(EXTRA_DOCUMENT_DB_ID);
		}

	}

	private void initializeWorkerFragment() {

		FragmentManager fragmentMan = getFragmentManager();

		documentWorkerFragment = (DocumentWorkerFragment) fragmentMan
		        .findFragmentByTag(DocumentViewFragment.TAG);

		if (documentWorkerFragment == null) {

			String server = currentUser.getAccountServer();
			// initialize worker fragment
			documentWorkerFragment = DocumentWorkerFragment.createInstance(getApplicationContext(),
			        server);
			fragmentMan.beginTransaction().add(documentWorkerFragment, DocumentViewFragment.TAG)
			        .commit();
		}

		documentWorkerFragment.initialize(documentType, formDbId, documentDbId);

	}

	// }}

	// ==========================================================================================
	// {{ View Implementation Methods

	@Override
	public void onMarkDocumentStarCommand(DocumentStar star) {

		String userDbId = Integer.toString(currentUser.getId());
		documentWorkerFragment.markDocumentStar(userDbId, star);

		startMarkDocumentStarService(star);

		if (star == DocumentStar.STARRED) {
			documentViewFragment.displayDocumentAsStarred();
		} else {
			documentViewFragment.displayDocumentAsUnstarred();
		}

	}

	@Override
	public void onOpenCommentsCommand() {
		Intent intent = new Intent(DocumentActivity.this, CommentsActivity.class);

		Document currentDocument = documentWorkerFragment.getCurrentDocument();

		intent.putExtra(CommentsActivity.EXTRA_CURRENT_USER, currentUser);
		intent.putExtra(CommentsActivity.EXTRA_CURRENT_DOCUMENT_FORM_WEB_ID,
		        currentDocument.getFormWebId());
		intent.putExtra(CommentsActivity.EXTRA_CURRENT_DOCUMENT_WEB_ID, currentDocument.getWebId());

		startActivity(intent);
		this.overridePendingTransition(R.animator.push_up_in, R.animator.push_up_out);
	}

	@Override
	public void onOpenPickerViewCommand(DateTimePickerField source, PickerType pickerType,
	        String currentFieldValue) {

		DateTimePickerType viewPickerType = null;
		int requestCode = 0;

		switch (pickerType) {
			case DATE: {
				viewPickerType = DateTimePickerType.DATE_ONLY;
				requestCode = DateTimePickerActivity.RC_PICK_DATE;
			}
				break;
			case TIME: {
				viewPickerType = DateTimePickerType.TIME_ONLY;
				requestCode = DateTimePickerActivity.RC_PICK_TIME;
			}
				break;
			case DATETIME: {
				viewPickerType = DateTimePickerType.DATE_TIME;
				requestCode = DateTimePickerActivity.RC_PICK_DATE_TIME;
			}
				break;
		}

		if (viewPickerType != null && requestCode != 0) {

			Intent dateTimePicker = new Intent(DocumentActivity.this, DateTimePickerActivity.class);

			dateTimePicker.putExtra(DateTimePickerActivity.EXTRA_DATE_TIME_PICKER_FIELD_ID,
			        source.getFieldId());
			dateTimePicker.putExtra(DateTimePickerActivity.EXTRA_DATE_TIME_PICKER_TYPE,
			        viewPickerType);
			dateTimePicker.putExtra(DateTimePickerActivity.EXTRA_PRE_SELECTED_DATE,
			        currentFieldValue);

			startActivityForResult(dateTimePicker, requestCode);
		}

	}

	@Override
	public void onScanCodeCommand(BarcodeScannerField source) {

		activeBarcodeScannerFieldId = source.getFieldId();

		IntentIntegrator.initiateScan(DocumentActivity.this, R.layout.activity_zxing_capture,
		        R.id.viewfinder_view, R.id.preview_view, true);
	}

	// }}

	// ========================================================================
	// {{ Functional Methods

	private void onSubmitActionCommand() {

		// validate first if there are no
		// Document updatedDocument = documentViewFragment.updateDocumentValuesFromView();
		JSONObject updatedFieldValues = documentViewFragment.getUpdatedFieldValuesFromView();
		// FLLogger.d(TAG, updatedFieldValues.toString());
		List<String> fieldsThatFailedValidation = documentWorkerFragment.validateFields(
		        updatedFieldValues, documentType);

		if (fieldsThatFailedValidation.isEmpty()) {
			showActionsDialog();
		} else {
			documentViewFragment.notifyFieldsRequired(fieldsThatFailedValidation);
			Toast.makeText(this, "Some fields have failed validation.", Toast.LENGTH_LONG).show();
		}

	}

	public void submitDocumentAction(String action) {

		// extract data from document view
		// Document updatedDocument = documentViewFragment.updateDocumentValuesFromView();

		JSONObject updatedFieldValues = documentViewFragment.getUpdatedFieldValuesFromView();
		documentWorkerFragment.saveDocumentAction(updatedFieldValues, action,
		        Integer.toString(currentUser.getId()));

		// TODO: add broadcast listeners

		Intent outgoingDocumentsActionSubmitServiceIntent = new Intent(DocumentActivity.this,
		        OutgoingDocumentActionSubmitService.class);

		outgoingDocumentsActionSubmitServiceIntent.putExtra(
		        OutgoingDocumentActionSubmitService.EXTRA_CURRENT_USER, currentUser);

		startService(outgoingDocumentsActionSubmitServiceIntent);

		Toast.makeText(
		        DocumentActivity.this,
		        "Your document is being processed. You may continue processing/viewing other documents while its sending.",
		        Toast.LENGTH_LONG).show();
		finish();

	}

	private void updateDateTimePickerView(int sourceRequestCode, String fieldId, Date updatedDate) {

		if (updatedDate == null) {
			FLLogger.w(TAG, "No date selected");
		} else {
			SimpleDateFormat dateFormat;

			switch (sourceRequestCode) {
				case DateTimePickerActivity.RC_PICK_DATE:
					dateFormat = DateUtilities.DEFAULT_DISPLAY_DATE_ONLY_FORMAT;
					break;
				case DateTimePickerActivity.RC_PICK_TIME:
					dateFormat = DateUtilities.DEFAULT_DISPLAY_TIME_ONLY_FORMAT;
					break;
				case DateTimePickerActivity.RC_PICK_DATE_TIME:
					dateFormat = DateUtilities.DEFAULT_DISPLAY_DATE_TIME_FORMAT;
					break;
				default: {
					FLLogger.w(TAG, "Invalid request code, the update was ignored");
					return;
				}

			}

			String displayDate = dateFormat.format(updatedDate);
			documentViewFragment.updateDateTimeFieldValue(fieldId, displayDate);

		}

	}

	private void registerBroadcastReceivers() {

		// register mark document star broadcast receiver

		IntentFilter documentStarBroadcastReceiverFilter = new IntentFilter();

		// @formatter:off
		documentStarBroadcastReceiverFilter.addAction(OutgoingStarMarksSubmitService.ACTION_ON_SERVICE_STARTED);
		documentStarBroadcastReceiverFilter.addAction(OutgoingStarMarksSubmitService.ACTION_ON_SERVICE_DONE);
		documentStarBroadcastReceiverFilter.addCategory(Intent.CATEGORY_DEFAULT);
		// @formatter:on

		registerReceiver(markDocumentStarBroadcastReceiver, documentStarBroadcastReceiverFilter);

	}

	private void unregisterBroadcastReceivers() {

		unregisterReceiver(markDocumentStarBroadcastReceiver);

	}

	// }}

	// ========================================================================
	// {{ Custom Implementation Functions

	public void updateFieldValue(FormField.FieldType fieldType, String fieldId, String fieldValue) {

		documentViewFragment.updateFieldValue(fieldType, fieldId, fieldValue);

	}

	public void openNewBlankForm() {
		Intent documentViewActivity = new Intent(DocumentActivity.this, DocumentActivity.class);

		documentViewActivity.putExtra(DocumentActivity.EXTRA_CURRENT_USER, currentUser);
		documentViewActivity.putExtra(DocumentActivity.EXTRA_DOCUMENT_TYPE,
		        DocumentType.NEW_DOCUMENT);
		documentViewActivity.putExtra(DocumentActivity.EXTRA_FORM_DB_ID, formDbId);

		startActivity(documentViewActivity);
	}

	// }}

	// ==========================================================================================
	// {{ Service Methods

	private void startMarkDocumentStarService(DocumentStar star) {

		Intent markDocumentStarService = new Intent(DocumentActivity.this,
		        OutgoingStarMarksSubmitService.class);

		markDocumentStarService.putExtra(OutgoingStarMarksSubmitService.EXTRA_CURRENT_USER,
		        currentUser);
		startService(markDocumentStarService);

	}

	// }}

	// ==========================================================================================
	// {{ Service Broadcast Receivers

	private BroadcastReceiver markDocumentStarBroadcastReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			// sUpdateBroadcastReceiverFilter.addAction(MarkDocumentStarService.ACTION_ON_SERVICE_STARTED);
			// formsUpdateBroadcastReceiverFilter.addAction(MarkDocumentStarService.ACTION_ON_SERVICE_DONE);

			String action = intent.getAction();

			if (OutgoingStarMarksSubmitService.ACTION_ON_SERVICE_STARTED.equals(action)) {
				FLLogger.d(TAG, "MarkDocumentStarService started");
			} else if (OutgoingStarMarksSubmitService.ACTION_ON_SERVICE_DONE.equals(action)) {

				// Bundle extras = intent.getExtras();
				//
				// boolean markingSuccess = extras
				// .getBoolean(MarkDocumentStarService.EXTRA_MARK_SUCCESSFUL);
				//
				// if (markingSuccess) {
				//
				// DocumentStar newStar = (DocumentStar) extras
				// .getSerializable(MarkDocumentStarService.EXTRA_NEW_DOCUMENT_MARK);
				//
				// if (newStar == DocumentStar.STARRED) {
				// documentViewFragment.displayDocumentAsStarred();
				// } else {
				// documentViewFragment.displayDocumentAsUnstarred();
				// }
				//
				// documentWorkerFragment.markCurrentDocumentStar(newStar);
				// } else {
				// documentViewFragment.displayErrorMarkingDocumentStar();
				// }

			} else {
				FLLogger.d(TAG, "markDocumentStarBroadcastReceiver received unhandled action: "
				        + action);
			}

		}

	};

	// }}

	// ==========================================================================================
	// {{ Getters And Setters (Local API)

	public Form getCurrentForm() {
		return documentWorkerFragment.getCurrentForm();
	}

	public Document getCurrentDocument() {
		return documentWorkerFragment.getCurrentDocument();
	}

	// }}

}
